// created by noellynch
// Jun 4, 2009

package com.sceneworker.editors.glslshadereditor;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.net.URL;
import java.util.concurrent.Callable;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;

import net.miginfocom.swing.MigLayout;

import org.lwjgl.opengl.ARBFragmentShader;
import org.lwjgl.opengl.ARBVertexShader;

import com.jme.scene.state.GLSLShaderObjectsState;
import com.jme.util.GameTaskQueueManager;
import com.jme.util.shader.ShaderVariable;
import com.myjavautils.GenericUtils;
import com.myjavautils.swing.MigLayoutUtils;
import com.myjavautils.swing.SimpleAlert;
import com.myjavautils.swing.SwingComponentFactory;
import com.sceneworker.app.globals.SceneWorkerDialogManager;
import com.sceneworker.app.globals.SceneWorkerGlobals;
import com.sceneworker.editors.glslshadereditor.glslcodepanel.GLSLCodePanel;
import com.sceneworker.editors.glslshadereditor.shadersnippets.ShaderSnippet;
import com.sceneworker.editors.glslshadereditor.shadersnippets.ShaderSnippetManager;

public class ShaderEditorPanel extends JPanel {

	private		GLSLShaderObjectsState		mcl_shaderState;
	private		JDialog						mcl_parDlg;
	
	private 	GLSLCodePanel 				mcl_vertexCode;
	private 	GLSLCodePanel 				mcl_fragCode;
	private		UniformsPanel				mcl_uniforms;
	
	private		JTextArea					mcl_errorText;
	
	private		boolean						mb_vertexCompiledOK;
	private		boolean						mb_fragmentCompiledOK;
	
	public	ShaderEditorPanel(JDialog cl_dlg, GLSLShaderObjectsState cl_shader) {
		mcl_shaderState = cl_shader;
		mcl_parDlg = cl_dlg;
		
		setLayout(new MigLayout("inset 5"));
		
		initialiseMenu();
		
		initialiseTabs();
		
		initialiseErrorArea();
		
		initialiseControlButtons();
	}
	
	private	void	initialiseMenu() {
		JMenuBar		lcl_menuBar = new JMenuBar();
		mcl_parDlg.setJMenuBar(lcl_menuBar);
		
		initialiseFileMenu();
		initialiseSnippetsMenu();
	}
	
	private	void	initialiseFileMenu() {
		JMenu			lcl_fileMenu = new JMenu("File");
		JMenuItem		lcl_loadVertex = new JMenuItem("Load Vertex Shader");
		lcl_loadVertex.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				loadShader(mcl_vertexCode);				
			}
		});
		
		JMenuItem		lcl_loadFragment = new JMenuItem("Load Fragment Shader");
		lcl_loadFragment.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				loadShader(mcl_fragCode);
			}
		});
		JMenuItem		lcl_saveShaders = new JMenuItem("Save Shaders");
		lcl_saveShaders.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if((mcl_shaderState.getVertexShader() == null) || (mcl_shaderState.getFragmentShader() == null)) {
					SimpleAlert.doAlert("Shaders are null. Check to see if they have compiled and been applied.", null);
					return;
				}
				
				JDialog		lcl_dlg = SceneWorkerDialogManager.inst().makeADialog(SceneWorkerDialogManager.SHADER_EXPORT_DIALOG, mcl_parDlg);
				ExportShaderPanel		lcl_panel = new ExportShaderPanel(lcl_dlg, mcl_shaderState.getVertexShader(), 
						mcl_shaderState.getFragmentShader());
				lcl_dlg.add(lcl_panel);
				lcl_dlg.pack();
				lcl_dlg.setVisible(true);
			}
		});
		
		lcl_fileMenu.add(lcl_loadVertex);
		lcl_fileMenu.add(lcl_loadFragment);
		lcl_fileMenu.add(lcl_saveShaders);
		
		mcl_parDlg.getJMenuBar().add(lcl_fileMenu);
	}
	
	private	void	initialiseSnippetsMenu() {
		ActionListener		lcl_a = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				ShaderSnippet		lcl_s = (ShaderSnippet)ShaderSnippetManager.inst().getSnippetByTitle(e.getActionCommand());
				lcl_s.initialiseUniforms(mcl_shaderState, new ShaderSnippet.IUniformsCreated() {
					public	void	uniformsCreated(ShaderSnippet cl_owner) {
						mcl_uniforms.updateUniformsList();
					}
				});
				
				loadShaderPair(SceneWorkerGlobals.SCENEWORKER_SHADER_DIR + lcl_s.getLocation() + "/" + lcl_s.getName());
			}
		};
		
		mcl_parDlg.getJMenuBar().add(ShaderSnippetManager.inst().initialiseASnippetMenu(lcl_a));
	}
	
	private	void	initUniformsList() {
		mcl_uniforms = new UniformsPanel(mcl_shaderState);
	}
	
	private	void	initialiseTabs() {
		initVertexShaderPanel();
		initFragmentShaderPanel();
		initUniformsList();
		
		JTabbedPane		lcl_tabbedPane = new JTabbedPane();
		lcl_tabbedPane.addTab("Vertex", mcl_vertexCode);
		lcl_tabbedPane.addTab("Fragment", mcl_fragCode);
		
		JSplitPane		lcl_splitter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, mcl_uniforms, lcl_tabbedPane);
		lcl_splitter.setDividerLocation(200);
		lcl_splitter.setOneTouchExpandable(true);
		JPanel			lcl_panel = new JPanel(new MigLayout("inset 5", "[grow,fill]"));
		lcl_panel.add(lcl_splitter, "w 100%, h 100%");
		lcl_panel.setMinimumSize(new Dimension(400, 300));
		add(lcl_panel, "width 100%, height 75%, wrap");
	}
	
	private	void	initialiseControlButtons() {
		JButton		lcl_done = SwingComponentFactory.makeAButton("Done");
		lcl_done.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				mcl_parDlg.setVisible(false);
			}
		});
		
		JButton		lcl_updateBtn = SwingComponentFactory.makeAButton("Update Shader State");
		lcl_updateBtn.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				updateShaders();
			}
		});
		
		JButton		lcl_compileFragmentShaderBtn = SwingComponentFactory.makeAButton("Compile Fragment Shader");
		lcl_compileFragmentShaderBtn.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				compileFragmentShader();
			}
		});
		
		JButton		lcl_compileVertexShaderBtn = SwingComponentFactory.makeAButton("Compile Vertex Shader");
		lcl_compileVertexShaderBtn.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				compileVertexShader();
			}
		});
		
		MigLayoutUtils.makeRightAlignedButtonBar(this, lcl_done, lcl_updateBtn, lcl_compileFragmentShaderBtn, lcl_compileVertexShaderBtn);
	}
	
	private	void	initVertexShaderPanel() {
		String		lstr_shader = mcl_shaderState.getVertexShader();
		
		if(lstr_shader == null) {
			mcl_vertexCode = new GLSLCodePanel("void main() { \n\tgl_FrontColor = gl_Color;\n\tgl_Position = ftransform();\n}");
		} else {
			mcl_vertexCode = new GLSLCodePanel(lstr_shader);
		}
		
		mcl_vertexCode.format();
	}
	
	private	void	initFragmentShaderPanel() {
		String		lstr_shader = mcl_shaderState.getFragmentShader();
		
		if(lstr_shader == null) {
			mcl_fragCode = new GLSLCodePanel("void main() {\n\tgl_FragColor = gl_Color;\n}");
		} else {
			mcl_fragCode = new GLSLCodePanel(lstr_shader);
		}
		
		mcl_fragCode.format();
	}
	
	private	void	initialiseErrorArea() {
		JPanel		lcl_p = new JPanel();
		lcl_p.setLayout(new MigLayout("insets 5", "[grow, fill]"));
		
		mcl_errorText = new JTextArea();
		mcl_errorText.setTabSize(2);
		
		JScrollPane		lcl_scrollPane = new JScrollPane();
		lcl_scrollPane.setViewportView(mcl_errorText);
		
		lcl_p.add(lcl_scrollPane, "w 100%, h 100%");
		add(lcl_p, "w 100%, h 25%, wrap");
		
	}
	
	private	void	loadShader(GLSLCodePanel cl_codePanel) {
		File		lcl_file = GenericUtils.getAFile(null, false, false);
		
		if(lcl_file == null) {
			return;
		}
		
		loadShader(cl_codePanel, lcl_file);
	}
	
	private	void	loadShader(GLSLCodePanel cl_codePanel, File cl_f) {
		String		lstr_temp = GenericUtils.getATextFile(cl_f);
		cl_codePanel.setShaderCode(lstr_temp.toString());
		cl_codePanel.format();
	}
	
	private	void	loadShader(GLSLCodePanel cl_codePanel, URL cl_f) {
		String		lstr_temp = GenericUtils.getATextFile(cl_f);
		cl_codePanel.setShaderCode(lstr_temp.toString());
		cl_codePanel.format();
	}
	
	private	void	loadShaderPair(String str_location) {
		URL		lcl_vert = ShaderEditorPanel.class.getClassLoader().getResource(str_location + ".vert");
		URL		lcl_frag = ShaderEditorPanel.class.getClassLoader().getResource(str_location + ".frag");
		
		loadShader(mcl_vertexCode, lcl_vert);
		loadShader(mcl_fragCode, lcl_frag);
	}
	
	private	void	updateShaders() {
		if(!mb_vertexCompiledOK) {
			SimpleAlert.doAlert("Vertex shader has either not been compiled or has errors. Cannot update.", this);
			return;
		}
		
		if(!mb_fragmentCompiledOK) {
			SimpleAlert.doAlert("Fragment shader has either not been compiled or has errors. Cannot update.", this);
			return;
		}
		
		GameTaskQueueManager.getManager().update(new Callable<Object>() {
			public Object call() throws Exception {
				LWJGLShaderGLSLCompiler	lcl_compiler = new LWJGLShaderGLSLCompiler();
				String		lstr_temp = lcl_compiler.compileShader(mcl_vertexCode.getShaderCode(), ARBVertexShader.GL_VERTEX_SHADER_ARB);
				
				if(lstr_temp == null) {
					mb_vertexCompiledOK = true;
				} else {
					mcl_errorText.setText(lstr_temp);
					mb_vertexCompiledOK = false;
					SimpleAlert.doAlert("Vertex Shader has compile errors.", null);
					return null;
				}
				
				lstr_temp = lcl_compiler.compileShader(mcl_fragCode.getShaderCode(), ARBFragmentShader.GL_FRAGMENT_SHADER_ARB);
				
				if(lstr_temp == null) {
					mb_fragmentCompiledOK = true;
				} else {
					mcl_errorText.setText(lstr_temp);
					mb_fragmentCompiledOK = false;
					SimpleAlert.doAlert("Fragment Shader has compile errors.", null);
					return null;
				}
				
				for(ShaderVariable lcl_var : mcl_shaderState.getShaderUniforms()) {
					lcl_var.needsRefresh = true;
				}
				
				mcl_shaderState.load(mcl_vertexCode.getShaderCode(), mcl_fragCode.getShaderCode());
				
				mcl_errorText.setText("Shaders updated successfully.");
				return null;
			}
			
		});
		
		mcl_vertexCode.format();
		mcl_fragCode.format();
	}
	
	private	void	compileShader(final String str_code, final int i_type, final String str_successMsg) {
		GameTaskQueueManager.getManager().update(new Callable<Object>() {
			
			public Object call() throws Exception {
				LWJGLShaderGLSLCompiler	lcl_compiler = new LWJGLShaderGLSLCompiler();
				String		lstr_temp = lcl_compiler.compileShader(str_code, i_type);
				
				if(lstr_temp == null) {
					mcl_errorText.setText(str_successMsg);
					if(i_type == ARBVertexShader.GL_VERTEX_SHADER_ARB) {
						mb_vertexCompiledOK = true;
					} else {
						mb_fragmentCompiledOK = true;
					}
					
				} else {
					mcl_errorText.setText(lstr_temp);
					if(i_type == ARBVertexShader.GL_VERTEX_SHADER_ARB) {
						mb_vertexCompiledOK = false;
					} else {
						mb_fragmentCompiledOK = false;
					}
				}
				
				return null;
			}
			
		});
	}
	
	private	void	compileVertexShader() {
		compileShader(mcl_vertexCode.getShaderCode(), ARBVertexShader.GL_VERTEX_SHADER_ARB, "Vertex Shader compiled ok.");
		mcl_vertexCode.format();
	}
	
	private	void	compileFragmentShader() {
		compileShader(mcl_fragCode.getShaderCode(), ARBFragmentShader.GL_FRAGMENT_SHADER_ARB, "Fragment Shader compiled ok.");
		mcl_fragCode.format();
	}
}
